package org.singledog.form.builder.dataset;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.singledog.form.builder.MagicConstants;
import org.singledog.form.builder.SpringContextHolder;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

/**
 * @author adam
 */
@Slf4j
public class LocalSpringServiceDataSet extends AbstractDataSet {
	@Override
	public DataSetType supportDataSetType() {
		return DataSetType.SERVICE;
	}

	/**
	 * 对于 local service, params 格式为：
	 * 	1. params仅能有一个entry
	 * 	2. key是什么无关紧要, 但是value 一定要是List
	 * 	3. List里面的元素， 可以使JSONObject 也可以是JSONArray， 或者是真正的参数对象
	 *
	 * @param datasetCode code
	 * @param params param
	 * @return object
	 */
	@Override
	public Object queryData(String datasetCode, Map<String, Object> params) {
		DataSetMeta meta = getDataSetMeta(datasetCode);
		Assert.notNull(meta.getBeanName(), "Bean name can not be null !");
		Assert.notNull(meta.getMethodName(), "Bean method name can not be null !");
		Assert.notNull(meta.getBeanClass(), "Bean class can not be null !");

		Class clazz = null;
		try {
			clazz = Class.forName(meta.getBeanClass());
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e);
		}

		Object bean = getTargetObject(meta, clazz);

		try {
			Method method = queryMethod(clazz, meta);
			Object ret = invoke(bean, method, meta, params);
			return ret;
		} catch (Exception e) {
			log.error("Invoke datasource failed ! meta: {}, param: {}", JSON.toJSONString(meta), JSON.toJSONString(params));
			log.error(e.getMessage(), e);
		}

		return null;
	}

	protected Object getTargetObject(DataSetMeta meta, Class clazz) {
		Object bean = SpringContextHolder.getBean(clazz, meta.getBeanName());
		if (bean == null) {
			throw new RuntimeException("Can not found bean with name : " + meta.getBeanName() + ", class : " + meta.getBeanClass());
		}
		return bean;
	}

	private Method queryMethod(Class clazz, DataSetMeta meta) throws ClassNotFoundException, NoSuchMethodException {
		Class[] paramTypes = getParamTypes(meta);
		Method method = clazz.getDeclaredMethod(meta.getMethodName(), paramTypes);
		return method;
	}

	private Class[] getParamTypes(DataSetMeta meta) throws ClassNotFoundException {
		Class[] paramTypes = null;
		JSONArray array = meta.getParamClazzs();
		if (array != null && array.size() > 0) {
			paramTypes = new Class[array.size()];
			for (int i = 0; i < array.size(); i++) {
				paramTypes[i] = Class.forName(array.getString(i));
			}
		}
		return paramTypes;
	}

	private Object invoke(Object target, Method method, DataSetMeta dataSetMeta, Map<String, Object> params) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException {
		Type[] paramTypes = method.getGenericParameterTypes();
		if (ArrayUtils.isEmpty(paramTypes)) {
			return method.invoke(target, new Class[0]);
		}

		Object[] paramObjs = new Object[paramTypes.length];
		for (int i = 0; i < paramObjs.length; i++) {
			paramObjs[i] = null;
		}

		if (!CollectionUtils.isEmpty(params)) {
			List list = (List) params.get(MagicConstants.LOCAL_DATASET_PARAM);
			if (list.size() < paramTypes.length) {
				throw new IllegalArgumentException("real params size not match with method declare !");
			}

			for (int i = 0; i < paramObjs.length; i++) {
				paramObjs[i] = getParam(paramTypes[i], list.get(i));
			}
		}

		return method.invoke(target, paramObjs);
	}

	private Object getParam(Type targetType, Object param) {
		if (param == null) {
			return null;
		}

		Class targetClass = null;

		//不带泛型的
		if (targetType instanceof Class) {
			targetClass = (Class) targetType;
			if (targetClass.isArray() && (param instanceof JSONArray)) {
				return ((JSONArray) param).toJavaList(targetClass).toArray();
			} else if (List.class.isAssignableFrom(targetClass)  && (param instanceof JSONArray)) {
				return ((JSONArray) param).toJavaList(targetClass);
			} else {
				if (targetClass.isAssignableFrom(param.getClass())) {
					return param;
				}

				if (param instanceof JSONObject) {
					return ((JSONObject) param).toJavaObject(targetClass);
				}
			}
		}

		//带泛型的
		if (targetType instanceof ParameterizedType) {
			ParameterizedType pt = (ParameterizedType) targetType;
			// deal with list
			if (List.class.isAssignableFrom((Class<?>) pt.getRawType()) && (param instanceof JSONArray)) {
				return JSON.parseObject(((JSONArray) param).toJSONString(), targetType);
			} else if (param instanceof JSONObject) {
				return ((JSONObject) param).toJavaObject(targetType);
			}
		}

		log.error("LocalSpringDataSet:getParam:Error get param ! type:{}, param：{}", targetType, param);
		return null;
	}
}
